package com.yonyou.pmclouds.workflow.aspect;

import com.yonyou.pmclouds.basecom.entity.SuperVO;
import com.yonyou.pmclouds.basecom.exception.BusinessException;
import com.yonyou.pmclouds.basecom.exception.BusinessRuntimeException;
import com.yonyou.pmclouds.basecom.runtime.RuntimeEnvironment;
import com.yonyou.pmclouds.basecom.util.BeanHelper;
import com.yonyou.pmclouds.basecom.util.CommonFieldConst;
import com.yonyou.pmclouds.organization.entity.OrgConst;
import com.yonyou.pmclouds.organization.entity.OrganizationVO;
import com.yonyou.pmclouds.organization.rmiitf.OrganizationQuery;
import com.yonyou.pmclouds.project.entity.ProjectVO;
import com.yonyou.pmclouds.project.rmiitf.ProjectSimpleQuery;
import com.yonyou.pmclouds.workflow.entity.PMAssignInfo;
import com.yonyou.pmclouds.workflow.entity.ProcessDefinitionVO;
import com.yonyou.pmclouds.workflow.entity.StartFlowArgsVO;
import com.yonyou.pmclouds.workflow.service.rmiitf.IWorkFlowService;
import com.yonyou.pmclouds.workflow.service.rmiitf.ProcessDefinitionQueryService;
import com.yonyou.pmclouds.workflow.util.BillFieldNameFormatCoverUtil;
import com.yonyou.pmclouds.workflow.util.StartFlowExecutor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Objects;

@Aspect
@Slf4j
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class StartFlowAspect {

    @Autowired
    private ProcessDefinitionQueryService processDefinitionQueryService;
    @Autowired
    private IWorkFlowService workFlowService;
    @Autowired
    private ProjectSimpleQuery projectSimpleQuery;
    @Autowired
    private OrganizationQuery organizationQuery;
    @Autowired
    private StartFlowExecutor startFlowExecutor;

    @Around("@annotation(com.yonyou.pmclouds.workflow.aspect.PMApprove)")
    public Object startWorkFlow(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();

        checkArgsType(proceedingJoinPoint);
        SuperVO businessVO = (SuperVO) proceedingJoinPoint.getArgs()[0];
        String billType = (String)BeanHelper.getProperty(businessVO, BillFieldNameFormatCoverUtil.getBillTypeFieldName(businessVO));

        String processKey = getProcessKey(billType, businessVO);
        PMAssignInfo assignedInfo = businessVO.assignInfo;

        byte origBillStatus = 0;

        if (!StringUtils.isEmpty(processKey)) {

            String triggerMethod = getTrigger(proceedingJoinPoint);
            //
            SuperVO origVO = getOrigBuziVO(businessVO, billType, triggerMethod);

            String billStatusField = BillFieldNameFormatCoverUtil.getBillStatusFieldName(businessVO);
            origBillStatus = BeanHelper.getProperty(origVO, billStatusField) == null ?
                    (byte) 0 : (byte) BeanHelper.getProperty(origVO, billStatusField);

        }

        long businessStartTime = System.currentTimeMillis();
        Object returnArg = proceedingJoinPoint.proceed();
        log.debug("启动工作流时执行业务操作耗时：" + (System.currentTimeMillis() - businessStartTime));

        StartFlowArgsVO startFlowArgsVO = new StartFlowArgsVO();
        startFlowArgsVO.setOriginalStatus(origBillStatus);
        startFlowArgsVO.setBusinessVO((SuperVO) returnArg);
        startFlowArgsVO.setAssignInfo(assignedInfo);
        startFlowArgsVO.setProcessKey(processKey);
        startFlowArgsVO.setBillType(billType);
        startFlowArgsVO.setTenantId(RuntimeEnvironment.getTenantId());
        startFlowArgsVO.setUserId(RuntimeEnvironment.getLoginUserId());

        //将包含启动所需要的信息实体放到线程里面去执行审批流的启动。
        startFlowExecutor.addTask(startFlowArgsVO);

        afterFlowStarted(startFlowArgsVO);

        log.debug("启动工作流时保存单据共耗时：" + (System.currentTimeMillis() - startTime));

        return returnArg;
    }

    /**
     * 检查方法的入参、返回值是否符合要求
     *
     */
    private void checkArgsType(ProceedingJoinPoint proceedingJoinPoint) {
        Object[] methodArgs = proceedingJoinPoint.getArgs();

        if (ArrayUtils.isEmpty(methodArgs) || !(methodArgs[0] instanceof SuperVO))
            throw new BusinessRuntimeException("使用@PMApprove第一个入参需要为SuperVO");

        Class clazz = ((MethodSignature)proceedingJoinPoint.getSignature()).getReturnType();

        if(!SuperVO.class.isAssignableFrom(clazz)){
            throw new BusinessRuntimeException("使用@PMApprove,返回值需设定为SuperVO");
        }

    }

    /**
     * 获取业务数据对象
     */
    private SuperVO getOrigBuziVO(SuperVO buziVO, String billType, String triggerMethod) throws BusinessException {
        SuperVO origVO;
        // 非保存点触发的审批，需要重新获取业务VO，以保证业务信息完整
        if ("save".equals(triggerMethod)) {
            origVO = buziVO;
        } else {
            //获取完整VO实体
            origVO = workFlowService.getBuziVOForWorkFlow((String) buziVO.pkFiledValue(), billType);
        }
        return origVO;
    }

    /**
     * 获取流程定义key
     */
    private String getProcessKey(String billType, SuperVO businessVO) throws BusinessException {
        //获取流程对象
        ProcessDefinitionVO definitionVO = getProcessDefinitionVO(billType, businessVO);

        String processKey;
        if (definitionVO == null) {
            log.debug("单据未配置审批流");
            processKey = null;
        } else {
            processKey = definitionVO.getProcessKey();
        }
        return processKey;
    }

    /**
     * 获取触发审批的操作方式，包括保存save和驳回再提交commit
     */
    private String getTrigger(ProceedingJoinPoint proceedingJoinPoint) {
        PMApprove pmApprove = ((MethodSignature) (proceedingJoinPoint.getSignature())).getMethod().getAnnotation(PMApprove.class);

        if (pmApprove != null) {
            return pmApprove.trigger();
        }
        return "";
    }

    private ProcessDefinitionVO getProcessDefinitionVO(String billType, SuperVO businessVO) throws BusinessException {
        //首先获取项目ID
        String pkProject = (String)(BeanHelper.getProperty(businessVO, BillFieldNameFormatCoverUtil.getPkProjectFieldName(businessVO)));
        //获取项目对象
        ProjectVO projectVO = projectSimpleQuery.queryProject(pkProject);
        String pkOrg = null;
        if(projectVO != null) {
            // 项目表当中组织主键存储的是NC的主键，获取NC主键
            String pkSrc = projectVO.getPkOrg();
            //判断是否是NC项目，因为只有NC项目才有组织
            if(StringUtils.isNotEmpty(pkSrc) && !"~".equalsIgnoreCase(pkSrc)) {
                //查询到组织对象
                OrganizationVO organizationVO = organizationQuery.queryByPkSrc(RuntimeEnvironment.getTenantId(), pkSrc);
                if(organizationVO != null && Objects.equals(organizationVO.getEnableState(), OrgConst.ENABLE_STATE)) {
                    //判断组织是否开启，若开启则给pkOrg赋值
                    pkOrg = organizationVO.getPkOrganization();
                }
            }
        }

        if (StringUtils.isEmpty(billType))
            throw new BusinessException("请指明单据类型");
        //获取流程对象，因为再获取的SQL语句中已经写死之后获取状态为启用状态的流程对象，所以其实这个数组只有一个对象。
        ProcessDefinitionVO[] processDefinitionVOs = processDefinitionQueryService.queryProcDefVOByBilltype(billType,
                RuntimeEnvironment.getTenantId(), pkOrg);

        if (ArrayUtils.isEmpty(processDefinitionVOs)) {
            // 如果未找到组织配置的启用的审批流，则查找集团审批流
            if(pkOrg != null) {
                processDefinitionVOs = processDefinitionQueryService.queryProcDefVOByBilltype(billType,
                        RuntimeEnvironment.getTenantId(), null);
                if (ArrayUtils.isEmpty(processDefinitionVOs)) {
                    return null;
                }
            }else{
                return null;
            }
        }
        return processDefinitionVOs[0];
    }

    private void afterFlowStarted(StartFlowArgsVO startFlowArgsVO) throws BusinessException {
        workFlowService.afterFlowStarted(startFlowArgsVO);
    }

}


